using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using Phoenix.ActionResults;
using Phoenix.Commands;
using Phoenix.Diagnostics;
using Phoenix.Framework;
using Phoenix.Utilities;

namespace Phoenix.Frames
{
    public class InvokeActionBuilder<TController> : InvokeActionBuilder where TController : Controller
    {
        public InvokeActionBuilder(ViewModelBase viewModel)
            : base(viewModel)
        {
        }

        public InvokeActionBuilder(ControllerActionContext context)
			: base(context)
        {
        }

        //Hide overloads which do not make sense for Generic InvokeActionBuilder
        protected new Task InvokeAction(IContentFrame targetFrame, ControllerAction controllerAction, object[] parameters = null, InvokeOptions options = InvokeOptions.None)
        {
            return base.InvokeAction(targetFrame, controllerAction, parameters, options);
        }
        protected new Task InvokeAction(ControllerAction controllerAction, object[] parameters = null, InvokeOptions options = InvokeOptions.None)
        {
            return base.InvokeAction(controllerAction, parameters, options);
        }
        protected new Task<TResult> InvokeActionWithResult<TResult>(ControllerAction controllerAction, object[] parameters = null, InvokeOptions options = InvokeOptions.None)
        {
            return base.InvokeActionWithResult<TResult>(controllerAction, parameters, options);
        }
        protected new Task<TResult> InvokeActionWithResult<TResult>(IContentFrame targetFrame, ControllerAction controllerAction, object[] parameters = null, InvokeOptions options = InvokeOptions.None)
        {
            return base.InvokeActionWithResult<TResult>(targetFrame, controllerAction, parameters, options);
        }

        public Task InvokeAction(string actionName, object[] parameters = null, InvokeOptions options = InvokeOptions.None)
        {
            var controllerType = typeof(TController);
            var assembly = controllerType.Assembly.GetAssemblyName();
            return base.InvokeAction(new ControllerAction(typeof(TController).Name, actionName, assembly), parameters, options);
        }

        public Task InvokeAction(IContentFrame targetFrame, string actionName, object[] parameters = null, InvokeOptions options = InvokeOptions.None)
        {
            var controllerType = typeof(TController);
            var assembly = controllerType.Assembly.GetAssemblyName();
            return base.InvokeAction(targetFrame, new ControllerAction(typeof(TController).Name, actionName, assembly), parameters, options);
        }

        public Task<TResult> InvokeActionWithResult<TResult>(string actionName, object[] parameters = null, InvokeOptions options = InvokeOptions.None)
        {
            var controllerType = typeof(TController);
            var assembly = controllerType.Assembly.GetAssemblyName();
            return base.InvokeActionWithResult<TResult>(new ControllerAction(typeof(TController).Name, actionName, assembly), parameters, options);
        }

        public Task<TResult> InvokeActionWithResult<TResult>(IContentFrame targetFrame, string actionName, object[] parameters = null, InvokeOptions options = InvokeOptions.None)
        {
            var controllerType = typeof(TController);
            var assembly = controllerType.Assembly.GetAssemblyName();
            var controllerAction = new ControllerAction(controllerType.Name, actionName, assembly);
            return base.InvokeActionWithResult<TResult>(targetFrame, controllerAction, parameters, options);
        }

        public Task InvokeAction<TActionResult>(Expression<Func<TController, TActionResult>> actionSelector, InvokeOptions options = InvokeOptions.None)
            where TActionResult : ActionResult
        {
            var controllerType = typeof(TController);
            var assembly = controllerType.Assembly.GetAssemblyName();
            return InvokeExpression(controllerType.Name, GetExpressionFrom(actionSelector), assembly, options);
        }

        public Task InvokeAction<TActionResult>(IContentFrame targetFrame, Expression<Func<TController, TActionResult>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
            where TActionResult : ActionResult
        {
            var controllerType = typeof (TController);
            var assembly = controllerType.Assembly.GetAssemblyName();
            return InvokeExpression(targetFrame, controllerType.Name, GetExpressionFrom(actionSelector), assembly, options);
        }

        public Task InvokeAction<TActionResult>(IContentFrame targetFrame, Expression<Func<TController, Task<TActionResult>>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
            where TActionResult : ActionResult
        {
            var controllerType = typeof(TController);
            var assembly = controllerType.Assembly.GetAssemblyName();
            return InvokeExpression(targetFrame, controllerType.Name, GetExpressionFrom(actionSelector), assembly, options);
        }

        public Task InvokeAction<TActionResult>(Expression<Func<TController, Task<TActionResult>>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
            where TActionResult : ActionResult
        {
            var controllerType = typeof(TController);
            var assembly = controllerType.Assembly.GetAssemblyName();
            return InvokeExpression(controllerType.Name, GetExpressionFrom(actionSelector), assembly, options);
        }

        public Task<TResult> InvokeActionWithResult<TResult>(IContentFrame targetFrame, Expression<Func<TController, ActionResult>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
        {
            var controllerType = typeof(TController);
            var assembly = controllerType.Assembly.GetAssemblyName();
            return InvokeExpressionWithResult<TResult>(targetFrame, controllerType.Name, GetExpressionFrom(actionSelector), assembly, options);
        }

        public Task<TResult> InvokeActionWithResult<TResult>(Expression<Func<TController, Task<ActionResult>>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
        {
            var controllerType = typeof(TController);
            var assembly = controllerType.Assembly.GetAssemblyName();
            return InvokeExpressionWithResult<TResult>(controllerType.Name, GetExpressionFrom(actionSelector), assembly, options);
        }

        public Task<TResult> InvokeActionWithResult<TResult>(IContentFrame targetFrame, Expression<Func<TController, Task<ActionResult>>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
        {
            var controllerType = typeof(TController);
            var assembly = controllerType.Assembly.GetAssemblyName();
            return InvokeExpressionWithResult<TResult>(targetFrame, controllerType.Name, GetExpressionFrom(actionSelector), assembly, options);
        }

        public Task<TResult> InvokeActionWithResult<TActionResult, TResult>(IContentFrame targetFrame,
            Expression<Func<TController, Task<TActionResult>>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
        {
            var controllerType = typeof(TController);
            var assembly = controllerType.Assembly.GetAssemblyName();
            return InvokeExpressionWithResult<TResult>(targetFrame, controllerType.Name, GetExpressionFrom(actionSelector), assembly, options);
        }

        public Task<TResult> InvokeActionWithResult<TResult>(Expression<Func<TController, ActionResult>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
        {
            var controllerType = typeof(TController);
            var assembly = controllerType.Assembly.GetAssemblyName();
            return InvokeExpressionWithResult<TResult>(controllerType.Name, GetExpressionFrom(actionSelector), assembly, options);
        }

        public Task<TResult> InvokeActionWithResult<TActionResult, TResult>(Expression<Func<TController, TActionResult>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
            where TActionResult : ActionResult
        {
            var controllerType = typeof(TController);
            var assembly = controllerType.Assembly.GetAssemblyName();
            return InvokeExpressionWithResult<TResult>(controllerType.Name, GetExpressionFrom(actionSelector), assembly, options);
        }

        public Task<TResult> InvokeActionWithResult<TActionResult, TResult>(Expression<Func<TController, Task<TActionResult>>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
            where TActionResult : ActionResult
        {
            var controllerType = typeof(TController);
            var assembly = controllerType.Assembly.GetAssemblyName();
            return InvokeExpressionWithResult<TResult>(controllerType.Name, GetExpressionFrom(actionSelector), assembly, options);
        }

        public Task<TResult> InvokeActionWithResult<TActionResult, TResult>(IContentFrame targetFrame, Expression<Func<TController, TActionResult>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
            where TActionResult : ActionResult
        {
            var controllerType = typeof(TController);
            var assembly = controllerType.Assembly.GetAssemblyName();
            return InvokeExpressionWithResult<TResult>(targetFrame, controllerType.Name, GetExpressionFrom(actionSelector), assembly, options);
        }

        public AsyncDelegateCommand InvokeActionCommand(string actionName, object[] parameters = null,
            InvokeOptions options = InvokeOptions.None)
        {
            return new AsyncDelegateCommand(() => InvokeAction(actionName, parameters, options));
        }

        public AsyncDelegateCommand InvokeActionCommand(IContentFrame targetFrame, string actionName, object[] parameters = null,
            InvokeOptions options = InvokeOptions.None)
        {
            return new AsyncDelegateCommand(() => InvokeAction(targetFrame, actionName, parameters, options));
        }

        public AsyncDelegateCommand InvokeActionCommand<TActionResult>(Expression<Func<TController, TActionResult>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
            where TActionResult : ActionResult
        {
            return new AsyncDelegateCommand(() => InvokeAction(actionSelector, options));
        }

        public AsyncDelegateCommand InvokeActionCommand<TActionResult>(Expression<Func<TController, TActionResult>> actionSelector, Func<bool> canExecute, 
            InvokeOptions options = InvokeOptions.None)
            where TActionResult : ActionResult
        {
            return new AsyncDelegateCommand(() => InvokeAction(actionSelector, options), canExecute);
        }

        public AsyncDelegateCommand InvokeActionCommand<TActionResult>(IContentFrame targetFrame, Expression<Func<TController, TActionResult>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
            where TActionResult : ActionResult
        {
            return new AsyncDelegateCommand(() => InvokeAction(targetFrame, actionSelector, options));
        }

        public AsyncDelegateCommand<TArg> InvokeActionCommand<TArg>(IContentFrame targetFrame, Expression<Func<TController, TArg, ActionResult>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
        {
            return new AsyncDelegateCommand<TArg>(arg =>
            {
                var controllerType = typeof(TController);
                var assembly = controllerType.Assembly.GetAssemblyName();
                return InvokeExpression(typeof(TController).Name, GetExpressionFrom(actionSelector), arg, assembly, options);
            });
        }

        public AsyncDelegateCommand InvokeActionCommand<TActionResult>(IContentFrame targetFrame, Expression<Func<TController, Task<TActionResult>>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
            where TActionResult : ActionResult
        {
            return new AsyncDelegateCommand(() => InvokeAction(targetFrame, actionSelector, options));
        }

        public AsyncDelegateCommand InvokeActionCommand<TActionResult>(Expression<Func<TController, Task<TActionResult>>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
            where TActionResult : ActionResult
        {
            return new AsyncDelegateCommand(() => InvokeAction(actionSelector, options));
        }

        public AsyncDelegateCommand<TArg> InvokeActionCommand<TArg>(Expression<Func<TController, TArg, ActionResult>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
        {
            return new AsyncDelegateCommand<TArg>(arg =>
            {
                var controllerType = typeof(TController);
                var assembly = controllerType.Assembly.GetAssemblyName();
                return InvokeExpression(typeof(TController).Name, GetExpressionFrom(actionSelector), arg, assembly, options);
            });
        }

        public AsyncDelegateCommand<TArg> InvokeActionCommand<TArg>(Expression<Func<TController, TArg, Task<ActionResult>>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
        {
            return new AsyncDelegateCommand<TArg>(arg =>
            {
                var controllerType = typeof(TController);
                var assembly = controllerType.Assembly.GetAssemblyName();
                return InvokeExpression(typeof(TController).Name, GetExpressionFrom(actionSelector), arg, assembly, options);
            });
        }

        public AsyncDelegateCommand<TArg> InvokeActionCommand<TActionResult, TArg>(Expression<Func<TController, TArg, TActionResult>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
            where TActionResult : ActionResult
        {
            return new AsyncDelegateCommand<TArg>(arg =>
                    {
                        var controllerType = typeof (TController);
                        var assembly = controllerType.Assembly.GetAssemblyName();
                        return InvokeExpression(typeof (TController).Name, GetExpressionFrom(actionSelector), arg, assembly, options);
                    });
        }

        public AsyncDelegateCommand<TArg> InvokeActionCommand<TActionResult, TArg>(Expression<Func<TController, TArg, Task<TActionResult>>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
            where TActionResult : ActionResult
        {
            return new AsyncDelegateCommand<TArg>(arg =>
            {
                var controllerType = typeof(TController);
                var assembly = controllerType.Assembly.GetAssemblyName();
                return InvokeExpression(typeof(TController).Name, GetExpressionFrom(actionSelector), arg, assembly, options);
            });
        }

        public AsyncDelegateCommand<TArg> InvokeActionCommand<TArg>(IContentFrame contentFrame, Expression<Func<TController, TArg, Task<ActionResult>>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
        {
            return new AsyncDelegateCommand<TArg>(arg =>
            {
                var controllerType = typeof(TController);
                var assembly = controllerType.Assembly.GetAssemblyName();
                return InvokeExpression(contentFrame, typeof(TController).Name, GetExpressionFrom(actionSelector), arg, assembly, options);
            });
        }

        public AsyncDelegateCommand<TArg> InvokeActionCommand<TActionResult, TArg>(IContentFrame targetFrame, Expression<Func<TController, TArg, TActionResult>> actionSelector,
            InvokeOptions options = InvokeOptions.None)
            where TActionResult : ActionResult
        {
            return new AsyncDelegateCommand<TArg>(arg =>
            {
                var controllerType = typeof(TController);
                var assembly = controllerType.Assembly.GetAssemblyName();
                return InvokeExpression(targetFrame, typeof(TController).Name, GetExpressionFrom(actionSelector), arg, assembly, options);
            });
        }

        public AsyncDelegateCommand<TArg> InvokeActionCommand<TActionResult, TArg>(IContentFrame targetFrame, Expression<Func<TController, TArg,
            Task<TActionResult>>> actionSelector, InvokeOptions options = InvokeOptions.None)
            where TActionResult : ActionResult
        {
            return new AsyncDelegateCommand<TArg>(arg =>
            {
                var controllerType = typeof(TController);
                var assembly = controllerType.Assembly.GetAssemblyName();
                return InvokeExpression(targetFrame, typeof(TController).Name, GetExpressionFrom(actionSelector), arg, assembly, options);
            });
        }

#if WINDOWS_PHONE
        /// <summary>
        /// Generates a deep link url for a controller action.
        /// ex: Controller&lt;HomeController&gt;().DeepLinkFor(c=>c.DeepLink, new QueryStringArgumentsCollection())
        /// 
        /// Controller action must have the signature 'public ActionResult DeepLink(QueryStringArgumentsCollection args)'
        /// 
        /// Generated Url is will look like /Shell.xaml?deepLink=Controller.Action%3farg%3dvalue%26arg2%3dvalue2 or 
        /// string.Format("/Shell.xaml?deepLink={0}", HttpUtility.UrlEncode("Controller.Action?arg=value&arg2=value2"));
        /// 
        /// You can also get the deep link via ControllerAction.CreateFrom("Controller.Action?arg=value&arg2=value2").ToDeepLink()
        /// </summary>
        /// <param name="actionSelector"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public Uri DeepLinkFor(
            Expression<Func<TController, Func<QueryStringArgumentsCollection, ActionResult>>> actionSelector, 
            QueryStringArgumentsCollection parameters)
        {
            var controllerType = typeof(TController);
            var assembly = controllerType.Assembly.GetAssemblyName();

            return new ControllerAction(typeof (TController).Name, ActionName(actionSelector), assembly, parameters).ToDeepLink();
        }

        internal static string ActionName(LambdaExpression expression)
        {
            var unaryExpression = expression.Body as UnaryExpression;
            if (unaryExpression == null)
                throw ExpressionException();
            var methodCallExpression = unaryExpression.Operand as MethodCallExpression;
            if (methodCallExpression == null)
                throw ExpressionException();
            var arguments = methodCallExpression.Arguments as IList<Expression>;
            var methodInfoExpression = arguments.Single(s => s.Type == typeof(MethodInfo)) as ConstantExpression;
            if (methodInfoExpression == null)
                throw ExpressionException();
            var methodInfo = (MethodInfo)methodInfoExpression.Value;
            var actionName = methodInfo.Name;
            return actionName;
        }

        private static Exception ExpressionException()
        {
            throw new Exception("The lambda expression used for navigation could not be parsed.\r\n\r\n" +
                                "The lambda should be a expression using method grouping syntax, for example: 'x => x.Search'.");

        }
#endif
    }
}